package pl.elka.pszt.model.parser;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import pl.elka.pszt.model.Clause;
import pl.elka.pszt.model.Fact;
import pl.elka.pszt.model.KnowledgeElement;
import pl.elka.pszt.model.argument.Argument;
import pl.elka.pszt.model.argument.Argument.Type;
import pl.elka.pszt.model.argument.ArgumentHelper;
import pl.elka.pszt.model.literal.Literal;
import pl.elka.pszt.model.literal.LiteralHelper;
import pl.elka.pszt.utils.TextUtils;

/**
 * Parser bazy wiedzy oraz zapytan.
 * 
 * @author Andrzej Makarewicz
 * 
 */
public class Parser {
	
	private static String name = "[a-zA-Z]+\\w*";
	private static String regex = "([~-]?" + name + "){1}\\((((" + name + "){1},?)+){1}\\)";
	private static final Pattern CLAUSE_PATTERN = Pattern.compile(regex);
	
	private Parser() {}
	
	/**
	 * 
	 * @param matcher
	 * @param argumentHelper
	 * @param literalHelper
	 * @return literal lub <code>null</code> jesli sie nie udalo sparsowac
	 */
	private static Literal parseLiteral(Matcher matcher, ArgumentHelper argumentHelper, LiteralHelper literalHelper) {
		List<Argument> args = new ArrayList<Argument>();
		String[] argNames = matcher.group(2).split(",");
		for (String argName : argNames) {
			boolean isConstant = Character.isUpperCase(argName.charAt(0));
			args.add(argumentHelper.getOrCreateArgument(argName, isConstant ? Type.CONSTANT : Type.VARIABLE));
		}
		
		String literalName = matcher.group(1);
		boolean isNegative = literalName.startsWith("-");
		if (isNegative) literalName = literalName.substring(1);
		
		return literalHelper.createLiteral(literalName, !isNegative, args);
	}
	
	/**
	 * 
	 * @param str
	 * @param argumentHelper
	 * @param literalHelper
	 * @return fakt lub <code>null</code> jesli sie nie udalo sparsowac
	 */
	public static Fact parseFact(String str, ArgumentHelper argumentHelper, LiteralHelper literalHelper) {
		if (TextUtils.isEmpty(str)) return null;
		
		Matcher matcher = CLAUSE_PATTERN.matcher(str);
		if (matcher.find()) return new Fact(parseLiteral(matcher, argumentHelper, literalHelper));
		
		return null;
	}
	
	/**
	 * 
	 * @param str
	 * @param argumentHelper
	 * @param literalHelper
	 * @return klauzula koniunkcyjno-implikacyjna lub <code>null</code> jesli
	 *         sie nie udalo sparsowac
	 */
	public static Clause parseClause(String str, ArgumentHelper argumentHelper, LiteralHelper literalHelper) {
		if (TextUtils.isEmpty(str)) return null;
		
		Matcher matcher = CLAUSE_PATTERN.matcher(str);
		List<Literal> literals = new ArrayList<Literal>();
		while (matcher.find())
			literals.add(parseLiteral(matcher, argumentHelper, literalHelper));
		
		int size = literals.size();
		if (size < 2) return null;
		
		Literal conclusion = literals.get(size - 1);
		literals.remove(size - 1);
		return new Clause(new HashSet<Literal>(literals), conclusion);
	}
	
	/**
	 * Parsuje element wiedzy podejmujac decyzje czy jest to klauzula czy fakt
	 * na podstawie wystapienia ciagu <code>'=>'</code>
	 * 
	 * @param str
	 * @param argumentHelper
	 * @param literalHelper
	 * @return element wiedzy lub <code>null</code> jesli sie nie udalo
	 *         sparsowac
	 */
	public static KnowledgeElement parseKnowledgeElement(String str, ArgumentHelper argumentHelper,
			LiteralHelper literalHelper) {
		if (TextUtils.isEmpty(str)) return null;
		
		return str.contains("=>") ? parseClause(str, argumentHelper, literalHelper) : parseFact(str, argumentHelper,
				literalHelper);
	}
}
